#!/usr/bin/env python3
import argparse
import pathlib
import shutil
import subprocess
import sys
import tempfile
import os
import platform

BASE_PATH = pathlib.Path(__file__).resolve().parent
VENV_PATH = BASE_PATH / ".venv"


# Ansible changes a lot between releases and deprecates a lot of stuff each of
# them. Using a pinned ansible identical between all team members should
# reduce the churn.
def install_ansible():
    requirements = BASE_PATH / "requirements.txt"
    venv_requirements = VENV_PATH / "installed-requirements.txt"

    # Avoid installing ansible in the virtualenv multiple times
    if venv_requirements.exists() and \
            venv_requirements.read_bytes() == requirements.read_bytes():
        return

    print("creating a new virtual environment and install ansible in it...")
    shutil.rmtree(VENV_PATH, ignore_errors=True)
    subprocess.run([sys.executable, "-m", "venv", str(VENV_PATH)], check=True)
    subprocess.run([
        str(VENV_PATH / "bin" / "pip"), "install", "-r", str(requirements),
    ], check=True)
    shutil.copy(str(requirements), str(venv_requirements))


def install_ansible_galaxy_dependencies():
    requirements = BASE_PATH / "requirements.yml"
    venv_requirements = VENV_PATH / "installed-requirements.yml"

    # Avoid unnecessary work by checking if the requirements are already installed
    if venv_requirements.exists() and \
            venv_requirements.read_bytes() == requirements.read_bytes():
        return

    subprocess.run([sys.executable, "-m", "venv", str(VENV_PATH)], check=True)
    subprocess.run([
        str(VENV_PATH / "bin" / "ansible-galaxy"), "install", "-r", str(requirements),
    ], check=True)

    shutil.copy(str(requirements), str(venv_requirements))


def run_playbook(args):
    env_dir = BASE_PATH / "envs" / args.env
    tempdir = pathlib.Path(tempfile.mkdtemp())
    try:
        # Create a temporary directory merging together the chosen
        # environment, the chosen playbook and the shared files.
        (tempdir / "play").mkdir()
        (tempdir / "play" / "roles").symlink_to(BASE_PATH / "roles")
        (tempdir / "play" / "group_vars").symlink_to(BASE_PATH / "group_vars")
        (tempdir / "play" / "playbook.yml").symlink_to(
            BASE_PATH / "playbooks" / (args.playbook + ".yml")
        )
        (tempdir / "env").symlink_to(env_dir)
        (tempdir / "ansible.cfg").symlink_to(BASE_PATH / "ansible.cfg")

        # Finally invoke the ansible binary installed in the virtualenv
        ansible_args = [
            str(VENV_PATH / "bin" / "ansible-playbook"),
            "-i", str(tempdir / "env" / "hosts"),
            "--extra-vars", f"vars_environment={args.env}",
            str(tempdir / "play" / "playbook.yml"),
        ]
        if args.user is not None:
            ansible_args += ["-u", args.user]
        if args.start_at_task is not None:
            ansible_args += ["--start-at-task", args.start_at_task]
        if args.tags is not None:
            ansible_args += ["--tags", args.tags]
        if args.skip_tags is not None:
            ansible_args += ["--skip-tags", args.skip_tags]
        if args.check:
            ansible_args += ["--check"]
        if args.diff:
            ansible_args += ["--diff"]
        if args.verbose > 0:
            ansible_args += [f"-{'v' * args.verbose}"]

        env = os.environ.copy()
        # Set environment variable if running on macOS to avoid python crash
        if platform.system() == "Darwin":
            env["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "true"

        res = subprocess.run(ansible_args, cwd=str(tempdir), env=env)
        if res.returncode != 0:
            exit(1)
    finally:
        shutil.rmtree(str(tempdir), ignore_errors=True)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("env")
    parser.add_argument("playbook")
    parser.add_argument(
        "-u", "--user", help="user to log into instances as", default=None,
    )
    parser.add_argument(
        "--start-at-task", help="start at a task with the provided name",
        default=None,
    )
    parser.add_argument(
        "--tags", help="run only these tags",
        default=None,
    )
    parser.add_argument(
        "--skip-tags", help="do not run these tags",
        default=None,
    )
    parser.add_argument(
        "--check", help="perform an Ansible check run",
        action="store_true",
    )
    parser.add_argument(
        "--diff", help="perform an Ansible diff run",
        action="store_true",
    )
    parser.add_argument('-v', '--verbose', action='count', default=0)
    args = parser.parse_args()

    install_ansible()
    install_ansible_galaxy_dependencies()
    run_playbook(args)
